#include <stdio.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <unistd.h>
#include <iostream>
#include <signal.h>

using namespace std;

//signal函数
//功能:
//    修改进程对特定信号的默认处理动作
//原型:
//    typedef void (*signalhandler_t)(int)
//    signalhandler_t signal(int signum, signalhandler_t handler)
//参数:
//    signum是信号的编号,可以传递数字,也可以传递宏
//    handler是一个参数为int,返回值为void的函数指针,这个函数是自己实现,用来自定义信号的处理动作
//返回值:
//    成功时,返回指向前一个指向此信号的处理函数的指针,错误是返回SIG_ERR
//PS:这里的SIG_ERR在linux里面是这样定义的#define (void (*)())-1
//为什么不这样定义?#define (void (*)(int))-1
//c语言是可以这样定义的
//void fun();
//
//void fun(int i, int j)
//{
//    cout << i << " " << j << endl;
//}
//
//int main()
//{
//    fun(1,2);
//}
//这里只是将-1强制转换为一个函数指针,好作为返回值,可以将SIG_ERR理解为一个整数,就像#define NULL (void*)0


//signal返回值的验证,并且通过signal注册(也就是我们调用的函数)对2号信号的处理动作,改成自定义动作
//注册函数的时候,不是调用这个函数,只有当这个函数来到的时候,这个函数才会被调用
//void fun1(int){
//    cout<<"fun1"<<endl;
//}
//
//void fun2(int){
//    cout<<"fun2"<<endl;
//}
//
//int main()
//{
//    void (*res)(int);
//    if( (res=signal(SIGINT,fun1)) == SIG_ERR ){
//        perror("error!");
//        return -1;
//    }
//    //raise是自己向自己的进程发送特定的信号
//    raise(SIGINT);// == ctrl+c
//
//    if( (res=signal(SIGINT,fun2)) == SIG_ERR ){
//        perror("error!");
//        return -1;
//    }
//    raise(SIGINT);// == ctrl+c
//    res(SIGINT);//signal返回 指向前一个处理SIGINT的函数 的指针,也就是指向fun1
//    std::cout << SIGINT << std::endl;
//    return 0;
//}

//void fun1(int){
//    cout<<"fun1"<<endl;
//}
//
//void fun2(int){
//    cout<<"fun2"<<endl;
//}
//
//void fun3(int)
//{
//    cout << "fun3" << endl;
//}
//
//int main()
//{
//    typedef void (*sig_type)(int);
//    sig_type res;
//    if( (res=signal(SIGINT,fun1)) == SIG_ERR ){
//        perror("error!");
//        return -1;
//    }
//    //raise是自己向自己的进程发送特定的信号
//    raise(SIGINT);// == ctrl+c
//
//    if( (res=signal(SIGINT,fun2)) == SIG_ERR ){
//        perror("error!");
//        return -1;
//    }
//    raise(SIGINT);// == ctrl+c
//    res(SIGINT);//signal返回 指向前一个处理SIGINT的函数 的指针,也就是指向fun1
//
//    if ((res = signal(SIGINT, fun3)) == SIG_ERR)
//    {
//        perror("signal");
//        return 1;
//    }
//    raise(SIGINT);
//    res(SIGINT);//signal返回 指向前一个处理SIGINT的函数 的指针,也就是指向fun2
//    
//    return 0;
//}

//信号的产生方式1:通过键盘产生
//实质:是键盘输入的命令被操作系统识别到,然后被解释成信号,向目标进程发送
//键盘产生的信号,只能用来终止前台进程
//一般而言,进程收到信号的处理方案有3种情况
//1.默认动作->结束进程,暂停进程
//2.忽略动作->也是信号的处理方式,就是什么也不干
//3.自定义动作(也叫做信号的捕捉)->我们上面用的signal方法,就是在修改信号的处理动作:默认动作->自定义动作

//ps:
//    1.ctrl c产生的信号只能发送给前台进程,一个命令后面加个&可以放到后台运行,这样shell不必等待进程结束就可以接收新的命令,启动新的进程
//    2.shell可以同时有运行一个前台进程和多个后台进程,只有前台进程才能接收到ctrl c这样控制键产生的信号
//    3.前台进程在运行的过程中用户随时可能按下ctrl c而产生一个信号,也就是说进程的用户空间代码执行的任何地方都有可能收到SIGINT信号而终止,所以信号相对于进程的控制流程来说是异步的
//    4.信号是进程之间事件异步通知的一种方式,属于软中断

//void handle(int sig_num)
//{
//    cout << "process pid : " << getpid() <<" sig_num : " << sig_num << endl;
//}
//
//int main()
//{
//    //将所有的信号都进行捕捉
//    int sig_num = 1;
//    for(; sig_num <= 31; ++sig_num)
//    {
//        signal(sig_num, handle);
//    }
//
//    while(1)
//    {
//        cout << "hello linxiaolei!" << endl;
//        sleep(2);
//    }
//}

//信号产生的方式2:进程存在异常,导致我们收到信号退出
//实质:进程发生异常,比如野指针的访问,比如除零的操作,会导致硬件的异常
//而操作系统是硬件的管理者,需要对硬件的健康进行负责,操作系统知道是哪个进程引起的错误,所以会向哪个进程发送信号
//在windows和linux下,进程崩溃的本质,是进程收到了对应的信号,然后进程执行信号的默认处理动作(杀死进程)

//int main()
//{
//    while(1)
//    {
//        //Floating point exception
//        //int a = 10;
//        //a /= 0;
//        
//        //Segmentation fault
//        int* p = nullptr;
//        *p = 100;
//        cout << "hello linxiaolei!" << endl;
//        sleep(2);
//    }
//}

//当子进程崩溃的时候,可以通过waitpid()里面的status,知道进程退出的信号和退出码
//除此之外,我还想得到是哪一行崩溃了
//所以这个时候status里面还有一个东西就派上用场了
//就是core dump标志位
//status低7位保存的是进程退出的信号,次低8位位保存的是进程的退出码
//第8位就是core dump标志位,如果进程异常退出,这个core dump标志位会被设置为1,不然默认就是0
//ps:不是所有进程异常退出都会被设置core dump标志位

//在linux中,当一个进程正常退出的时候,它的退出码和退出信号都会被设置
//当一个进程异常退出的时候,进程的退出信号会被设置,而退出码不回被设置,默认为0
//如果有必要,OS会设置退出信息中core dump标志位,并将进程在内存中的数据转移到磁盘中,生成core dump文件,方便我们后期调试

//core dump文件生成的功能,在服务器上是默认关闭的,需要自己打开
//查看core文件生成功能是否打开，如下若是0，则表示没有打开。
//ulimit -a
//临时设置（如下设置2G，单位为kbyte）
//如果生成的信息超过此大小，将会被裁剪，最终生成一个不完整的core文件。在调试此core文 件的时候，gdb会提示错误。
//ulimit -c 4194304
//永久设置
//echo " * soft core 4194304" >> /etc/security/limits.conf
//echo " * hard core 4194304" >> /etc/security/limits.conf

int main()
{
    if (fork() == 0)
    {
        cout << "i am child process!" << endl;
        int a = 10;
        a /= 0;
        return 1;
    }

    int status = 0;
    waitpid(-1, &status, 0);
    cout << "exit code:" << ((status >> 8) & 0xFF) << 
        " exit signal:" << (status & 0x7F) << 
        " core dump flag:" << ((status >> 7 & 1)) << endl;
}

//信号产生的方式3:进程通过系统调用,产生信号
//实质:通过系统调用,操作系统直接向目标进程发送信号

//下面说一下常见的发送信号的系统调用接口
//kill
//功能:
//    向一个指定的进程发送指定的信号
//原型:
//    int kill(pid_t pid, int signo)
//返回值:
//    成功返回0,失败返回-1
//ps:
//   kill命令是调用kill函数实现的

//raise
//功能:
//   可以给当前的进程发送指定的信号(自己给自己发信号)
//原型:
//   int raise(int signo)
//返回值:
//   成功返回0,失败返回-1

//abort
//功能:
//    使当前进程收到信号而异常终止
//原型:
//    void abort(void)
//ps:
//    类似于:exit函数,abort函数调用总是会成功的,所以没有返回值
//    就算捕捉了这个信号,执行了自定义动作后,最后还是执行退出

//void handler(int signo)
//{
//    cout << "signo:" << signo << endl;
//    sleep(2);
//}
//
//int main()
//{
//    for (int i = 1; i <= 31; ++i)
//    {
//        signal(i, handler);
//    }
//
//    cout << "main function!" << endl;
//    raise(2);
//    cout << "main function!" << endl;
//    abort();
//    return 0;
//}

//实现一个向指定进程发送指定信号的程序
//模拟实现kill
//static void user_page()
//{
//    cout << "user_page:pid,signo" << endl;
//}
//
//int main(int argc, char* argv[])
//{
//    if (argc != 3)
//    {
//        user_page();
//        return 1;
//    }
//    else if (atoi(argv[2]) > 31)
//    {
//        return 1;
//    }
//    pid_t pid  = atoi(argv[1]);
//    int signo = atoi(argv[2]);
//
//    kill(pid, signo);
//
//    cout << "pid:" << pid << " signo:" << signo << endl;
//
//    return 0;
//}

//信号产生的方式4:由软件条件,产生信号
//实质:通过某种软件(OS),来触发信号的发送,比如系统层面设置定时器,或者某种操作导致条件不就绪,触发信号的发送
//进程间通信:当读端关闭了文件描述符,而写端一直在写,最终写进程会收到SIGPIPE(13)信号,这个就是典型的软件条件触发的信号发送

//SIGPIPE是一种由软件条件产生的信号,在“管道”中已经介绍过了下面节主要介绍alarm函数和SIGALRM信号

//alarm
//功能:
//    设定一个闹钟,告诉内核在seconds秒后给当前进程发送SIGALRM信号,该信号默认处理动作是终止当前进程
//原型:
//    unsigned int alarm(unsigned int seconds);
//返回值:
//    返回之前任何alarm设置的剩余秒数,如果之前alarm没有设置过,那就返回0

//设置系统层面的定时器,来触发信号的发送
//void handler(int signo)
//{
//    cout << "signo:" << signo << endl;
//    sleep(60);
//}
//
//int main()
//{
//        signal(SIGALRM, handler);
//
//    unsigned int ret = alarm(10);
//
//    while(1)
//    {
//        cout << "i am a process, alarm ret:" << ret << endl; 
//        sleep(3);
//
//        ret = alarm(10);
//        cout << "i am a process, alarm ret:" << ret << endl; 
//        sleep(3);
//
//        //这里执行到sleep(60)的时候,几秒后收到了SIGALRM信号
//        //就去执行我们自定义的信号捕捉了
//        //之后返回的是下一行代码,不会继续执行sleep(60)
//        ret = alarm(5);
//        cout << "i am a process, alarm ret:" << ret << endl; 
//        sleep(60);
//        cout << "wait" << endl;
//        break;
//
//    }
//    return 0;
//}

//通过设置alarm,统计一下1s的时间
//如果是输出,一个数递增可以递增到多少
//如果是单独递增,只输出结果,可以递增多少
//int count = 0;
//
//void handler(int signo)
//{
//    cout << "count:" << count << endl;
//}
//
//int main()
//{
//    //输出的情况
//    //count的值达到了五六万的样子    
//    //alarm(1); //没有设置alarm信号的捕捉动作(在没有自定义),执行默认动作,终止进程
//    //while(1)
//    //{
//    //    //为何这里的count递增比较慢,因为有IO
//    //    cout << "count:" << count << endl;
//    //    count++;
//    //}
//    
//    //只递增,打印结果
//    //count的值达到了五六亿的样子
//    signal(SIGALRM, handler);
//    alarm(1);
//    while(1)
//    {
//        count++;
//    }
//    return 0;
//}
